package trees;


import java.io.PrintStream;
import java.util.HashMap;
import java.util.Vector;

import functors.BinaryFunctor;

public class MetricTree<T> {
	BinaryFunctor<T,T,Integer> metric;
	MetricTree<T> root;
	HashMap<Integer, MetricTree<T>> children;
	T content;
	int depth = 0; // only used for printing
	
	public MetricTree() {
		this.content = null;
		this.metric = null;
		this.children = new HashMap<Integer,MetricTree<T>>();
	}
	
	public MetricTree(BinaryFunctor<T,T,Integer> metric) {
		this.content = null;
		this.metric = metric;
		this.children = new HashMap<Integer,MetricTree<T>>();
	}
	
	public MetricTree(T term, BinaryFunctor<T,T,Integer> metric) {
		this.content = term;
		this.metric = metric;
		this.children = new HashMap<Integer,MetricTree<T>>();	
	}
	
	public MetricTree(T term) {
		this.content = term;
		this.metric = null;
		this.children = new HashMap<Integer,MetricTree<T>>();
	}
	
	public void add(T term) {
		/**
		 * add a new term to the MetricTree
		 */
		
		if (content == null) {
			content = term;
		} else {
			Integer distance = metric.fn(content, term);
			if (distance > 0) {
				MetricTree<T> child = children.get(distance);
				if (child == null) {
					children.put(distance, new MetricTree<T>(term, metric));
				} else {
					child.add(term);
				}
				
			}
		}
	}
	
	public Vector<Pair<Integer,T>> find(T term, Integer threshold) {
		Vector<Pair<Integer, T>> results = new Vector<Pair<Integer,T>>();
		
		_find(term, threshold, results);
		return results;
	}
	
	public void _find(T term, Integer threshold, Vector<Pair<Integer,T>> results) {
		Integer distance = metric.fn(content, term);
		Integer floor = distance - threshold;
		Integer ceil = distance + threshold;
		
		if (distance <= threshold) {
			results.add(new Pair<Integer,T>(distance, content));
		}
		
		
		
		for (Integer distance_of_node : children.keySet()) {
			if ( distance_of_node >= floor && distance_of_node <= ceil) {
				children.get(distance_of_node)._find(term, threshold, results);
			}
		}
	}
	
	public void printChildren(PrintStream out, MetricTree<T> obj) {
		depth++;
		String ws = "";
		for (int i = 0; i < depth; ++i) {
			ws += " ";
		}
		
		for (Integer distance : obj.getChildren().keySet()) {
			MetricTree<T> child = obj.getChildren().get(distance);
			
			out.printf("\n" + ws + "{:DISTANCE %d :VALUE %s :CHILDREN [ ", distance, child.getContent());
			this.printChildren(out, child);
			out.printf(ws + "]},\n");
			
		}depth--;
		
	}
	
	public void printObject(PrintStream out) {
		depth = 0;
		out.printf("{:DISTANCE 0 :VALUE %s :CHILDREN ", content);
		printChildren(out, this);
		out.printf("}\n");
		depth -= depth;
	}
	
	
	
	public T getContent() {
		return content;
	}
	
	public HashMap<Integer,MetricTree<T>> getChildren() {
		return children;
	}
	

}
